package com.zhangdi.dwdata.repository;

import com.zhangdi.dwdata.page.Page;
import com.zhangdi.dwdata.query.*;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.io.Serializable;
import java.util.List;

/**
 * Created by zhangdi on 2020/09/17 11:27:54.
 */
public class BaseRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements AbstractRepository<T, ID> {
    private final EntityManager em;

    public BaseRepositoryImpl(Class<T> clazz, EntityManager entityManager) {
        super(clazz, entityManager);
        this.em = entityManager;
    }

    private CriteriaBuilder builder(){
        return em.getCriteriaBuilder();
    }

    @Override
    public T getOne(QFinder q) {
        TypedQuery<T> query = createQuery(q);
        if (query.getResultList().size() == 0) {
            return null;
        }

        return query.getSingleResult();
    }

    @Override
    public T getOne(String property, Object val) {
        List<T> list = query(property, val);
        if (list.size() == 0) {
            return null;
        }

        return list.get(0);
    }

    @Override
    public List<T> query(List<Filter> filters) {
        Finder<T> finder = new Finder<>(builder(), getDomainClass());
        finder.setFilters(filters);
        return em.createQuery(this.createQuery(finder)).getResultList();
    }

    @Override
    public List<T> query(List<Filter> filters, List<Order> orders) {
        Finder<T> finder = new Finder<>(builder(), getDomainClass());
        finder.setFilters(filters);
        finder.setOrders(orders);
        return em.createQuery(this.createQueryByOrder(finder)).getResultList();
    }

    @Override
    public List<T> query(String property, Object val) {
        CriteriaBuilder builder = builder();
        Class<T> clazz = getDomainClass();
        CriteriaQuery<T> query = builder.createQuery(clazz);
        Root<T> root = query.from(clazz);
        return em.createQuery(query.select(root).where(builder.equal(root.get(property), val))).getResultList();
    }

    @Override
    public List<T> query(QFinder q) {
        return createQuery(q).getResultList();
    }

    @Override
    public Page<T> query(QFinder q, int pageNo, int pageSize) {
        TypedQuery<T> query = createQuery(q);
        query.setFirstResult(pageNo);
        query.setMaxResults(pageSize);

        Page<T> page = new Page<>();
        page.setNo(pageNo);
        page.setSize(pageSize);
        page.setTotal(totalCountQuery(q));
        page.setList(query.getResultList());

        return page;
    }

    @Override
    public TypedQuery<T> createQuery(QFinder q) {
        TypedQuery<T> query = em.createQuery(q.getHql(), getDomainClass());
        q.setQueryParams(query);
        return query;
    }

    @Override
    public Long totalCountQuery(QFinder q) {
        javax.persistence.Query query = em.createQuery(q.getRowCountHql());
        q.setQueryParams(query);
        return ((Number)query.getSingleResult()).longValue();
    }

    @Override
    public CriteriaQuery<T> createQuery(Finder<T> finder) {
        Predicate[] predicates = new Predicate[finder.getFilters().size()];
        int index = 0;
        for (Filter filter : finder.getFilters()) {
            if (filter.getOp() == Operator.eq) {
                predicates[index] = finder.getBuilder().equal(finder.getRoot().get(filter.getProperty()), filter.getVal());
            } else if (filter.getOp() == Operator.ne) {
                predicates[index] = finder.getBuilder().not(finder.getBuilder().equal(finder.getRoot().get(filter.getProperty()), filter.getVal()));
            } else if (filter.getOp() == Operator.lt) {
                predicates[index] = finder.getBuilder().lt(finder.getRoot().get(filter.getProperty()), (Number) filter.getVal());
            } else if (filter.getOp() == Operator.gt) {
                predicates[index] = finder.getBuilder().gt(finder.getRoot().get(filter.getProperty()), (Number) filter.getVal());
            } else if (filter.getOp() == Operator.lte) {
                predicates[index] = finder.getBuilder().le(finder.getRoot().get(filter.getProperty()), (Number) filter.getVal());
            } else if (filter.getOp() == Operator.gte) {
                predicates[index] = finder.getBuilder().ge(finder.getRoot().get(filter.getProperty()), (Number) filter.getVal());
            }
            index++;
        }

        return finder.getQuery().select(finder.getRoot()).where(predicates);
    }

    @Override
    public CriteriaQuery<T> createQueryByOrder(Finder<T> finder) {
        javax.persistence.criteria.Order[] orderlist = new javax.persistence.criteria.Order[finder.getOrders().size()];
        int index = 0;
        for (Order order : finder.getOrders()) {
            if (order.getDirection() == Direction.asc) {
                orderlist[index] = finder.getBuilder().asc(finder.getRoot().get(order.getProperty()));
            } else if (order.getDirection() == Direction.desc) {
                orderlist[index] = finder.getBuilder().desc(finder.getRoot().get(order.getProperty()));
            }
            index++;
        }
        return this.createQuery(finder).orderBy(orderlist);
    }
}
